﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Threading;

namespace Proxy
{
    
    public class BlackWhiteListManager
    {
        private long TimeLimit;
        private int MaxPacketPerTimeSlice;

        private string m_blackListPath;
        private string m_mailListPath;
        private string m_whiteListPath;
        private string m_passwordPath;
        private List<string> m_blackList;
        private List<string> m_whiteList;
        private string m_password;
        private ReaderWriterLock m_lock;

        private List<string> m_mails;
        
        private System.Collections.Hashtable m_connections;
        private ReaderWriterLock m_connectionLock;

        /// <summary>
        /// Creates an object that handles writing,reading and encrypting ip's.
        /// Handles security rules as well
        /// </summary>
        /// <param name="maxPackets">the maximum amount of requests allowed</param>
        /// <param name="timeLimit">the time limit in which max packets are allowed to be requested</param>
        public BlackWhiteListManager(int maxPackets, int timeLimit)
        {
            MaxPacketPerTimeSlice = maxPackets;
            TimeLimit = timeLimit*((long)Math.Pow(10, 7));
            m_blackListPath = Directory.GetCurrentDirectory() + ConfigurationManager.AppSettings["blacklist"];
            m_whiteListPath = Directory.GetCurrentDirectory() + ConfigurationManager.AppSettings["whitelist"];
            m_passwordPath = Directory.GetCurrentDirectory() + ConfigurationManager.AppSettings["password"];
            m_mailListPath = Directory.GetCurrentDirectory() + ConfigurationManager.AppSettings["mails"];
            m_blackList = new List<string>();
            m_whiteList = new List<string>();
            m_lock = new ReaderWriterLock();
            m_mails = new List<string>();
            m_connections = new System.Collections.Hashtable(new Dictionary<string, List<DateTime>>());
            m_connectionLock = new ReaderWriterLock();
            readPassword();
            decryptBlackList();
            decryptWhiteList();
        }

        /// <summary>
        /// adds an email to the mails.txt file
        /// </summary>
        /// <param name="mail">the mail to be added</param>
        /// <author>Etai Hazan</author>
        public void addMail(string mail)
        {
            m_lock.AcquireWriterLock(-1);
            if (!m_mails.Contains(mail))
            {
                StreamWriter writer = new StreamWriter(m_mailListPath, true);
                writer.WriteLine(mail);
                writer.Close();
                m_mails.Add(mail);
            }
            m_lock.ReleaseWriterLock();
            
        }
        public string Password { get{return m_password;} }

        /// <summary>
        /// calculates the number of requests in relation to the
        /// time ticks that passed
        /// </summary>
        /// <param name="ip">the ip to calculate the num of requests for</param>
        /// <returns>returns the number of requests</returns>
        /// <author>Etai Hazan</author>
        private int getNumOfRequests(string ip)
        {
            List<DateTime> l = (List<DateTime>) m_connections[ip];
            for (int i = l.Count - 1; i >= 0; i--)
            {

                try
                {
                    long time = DateTime.Now.Ticks - l[i].Ticks;
                    if (time > TimeLimit)
                        l.RemoveAt(i);
                    else
                        return l.Count;
                }
                catch 
                {
                    //l.RemoveAt(i);
                }
                
            }
            return 0;
        }
        /// <summary>
        /// checks if the number of requests exceeded the maximum allowed.
        /// removes ip from the whitelist if exceeded
        /// </summary>
        /// <param name="ip">the ip to check if exceeded</param>
        /// <returns>true if the ip exceeded, false otherwise</returns>
        /// <author>Etai Hazan</author>
        public bool exceedsSecurityRules(string ip)
        {
            if (m_connections.ContainsKey(ip))
            {
                if (getNumOfRequests(ip) >= MaxPacketPerTimeSlice)
                {
                    removeFromWhiteList(ip);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// adds an ip to the connections hashtable for follow up
        /// </summary>
        /// <param name="ip">the ip to add</param>
        /// <author>Etai Hazan</author>
        public void addToConnections(string ip)
        {
            if (m_connections.ContainsKey(ip))
            { 
                List<DateTime> l = (List<DateTime>) m_connections[ip];
                l.Add(DateTime.Now);
            }
            else
            {
                List<DateTime> l = new List<DateTime>();
                l.Add(DateTime.Now);
                if(!m_connections.Contains(ip))
                    m_connections.Add(ip, l);
            }
        }

        /// <summary>
        /// removes an ip from the whitelist
        /// and from the file
        /// </summary>
        /// <param name="ip">the ip to remove</param>
        /// <author>Shai Cantor</author>
        private void removeFromWhiteList(string ip)
        {
            m_lock.AcquireWriterLock(-1);
            if (m_whiteList.Contains(ip))
            {
                string encryptedIP = encryptString(ip);
                m_whiteList.Remove(ip);
                StreamWriter writer = new StreamWriter(m_whiteListPath);
                foreach (string IP in m_whiteList)
                    writer.WriteLine(encryptString(IP));
                m_connections.Remove(ip);
            }
            m_lock.ReleaseWriterLock();  
        }

        /// <summary>
        /// checks if the url is in the blacklist
        /// </summary>
        /// <param name="URL">the url to check</param>
        /// <returns>true if url is in the blacklist, false otherwise</returns>
        /// <author>Shai Cantor</author>
        public bool isRestrictedSite(string URL)
        {
            m_lock.AcquireReaderLock(-1);
            bool restricted = m_blackList.Contains(URL);
            m_lock.ReleaseReaderLock();
            return restricted;
        }

        /// <summary>
        /// checks if the ip is in the whitelist
        /// </summary>
        /// <param name="IP">the ip to check</param>
        /// <returns>true if ip is in the whitelist, false otherwise</returns>
        /// <author>Shai Cantor</author>
        public bool isAuthorized(string IP)
        {
            m_lock.AcquireReaderLock(-1);
            bool authorized = m_whiteList.Contains(IP);
            m_lock.ReleaseReaderLock();
            return authorized;
        }

        /// <summary>
        /// adds an ip to the whitelist
        /// and to the file
        /// </summary>
        /// <param name="IP">the ip to add</param>
        /// <author>Shai Cantor</author>
        public void addToWhiteList(string IP)
        {
            m_lock.AcquireWriterLock(-1);
            if (!m_whiteList.Contains(IP))
            {
                string encryptedIP = encryptString(IP);
                StreamWriter writer = new StreamWriter(m_whiteListPath, true);
                writer.WriteLine(encryptedIP);
                writer.Close();
                m_whiteList.Add(IP);
                addToConnections(IP);
            }
            m_lock.ReleaseWriterLock();
        }

        /// <summary>
        /// adds an ip to the blacklist
        /// and to the file
        /// </summary>
        /// <param name="site">the ip to add</param>
        /// <author>Shai Cantor</author>
        public void addToBlackList(string site)
        {
            m_lock.AcquireWriterLock(-1);
            if (!m_blackList.Contains(site))
            {
                string encryptedIP = encryptString(site);
                StreamWriter writer = new StreamWriter(m_blackListPath, true);
                writer.WriteLine(encryptedIP);
                writer.Close();
                m_blackList.Add(site);
            }
            m_lock.ReleaseWriterLock();
        }
        
        /// <summary>
        /// encrypt the message using the tripes DES algorithm with 
        /// the password written in the password.text file
        /// </summary>
        /// <param name="Message">the message to encrypt</param>
        /// <returns>the encrypted message</returns>
        /// <author>Shai Cantor</author>
        private string encryptString(string Message)
        {

            byte[] Results;

            System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
            // Step 1. We hash the passphrase using MD5
            // We use the MD5 hash generator as the result is a 128 bit byte array
            // which is a valid length for the TripleDES encoder we use below

            MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();

            byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(m_password));

            // Step 2. Create a new TripleDESCryptoServiceProvider object
            TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

            // Step 3. Setup the encoder

            TDESAlgorithm.Key = TDESKey;
            TDESAlgorithm.Mode = CipherMode.ECB;
            TDESAlgorithm.Padding = PaddingMode.PKCS7;

            // Step 4. Convert the input string to a byte[]
            byte[] DataToEncrypt = UTF8.GetBytes(Message);

            // Step 5. Attempt to encrypt the string
            try
            {
                ICryptoTransform Encryptor = TDESAlgorithm.CreateEncryptor();

                Results = Encryptor.TransformFinalBlock(DataToEncrypt, 0, DataToEncrypt.Length);
            }
            finally
            {
                // Clear the TripleDes and Hashprovider services of any sensitive information
                TDESAlgorithm.Clear();
                HashProvider.Clear();
            }

            // Step 6. Return the encrypted string as a base64 encoded string
            return Convert.ToBase64String(Results);
        }

        /// <summary>
        /// decrypt the message using the tripes DES algorithm with 
        /// the password written in the password.text file
        /// </summary>
        /// <param name="Message">the message to decrypt</param>
        /// <returns>the decrypted message</returns>
        /// <author>Shai Cantor</author>
        private string decryptString(string Message)
        {
            byte[] Results;

            System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

            // Step 1. We hash the passphrase using MD5
            // We use the MD5 hash generator as the result is a 128 bit byte array
            // which is a valid length for the TripleDES encoder we use below

            MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();

            byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(m_password));

            // Step 2. Create a new TripleDESCryptoServiceProvider object

            TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

            // Step 3. Setup the decoder

            TDESAlgorithm.Key = TDESKey;

            TDESAlgorithm.Mode = CipherMode.ECB;

            TDESAlgorithm.Padding = PaddingMode.PKCS7;

            // Step 4. Convert the input string to a byte[]

            byte[] DataToDecrypt = Convert.FromBase64String(Message);

            // Step 5. Attempt to decrypt the string

            try
            {
                ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
                Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
            }
            finally
            {
                // Clear the TripleDes and Hashprovider services of any sensitive information
                TDESAlgorithm.Clear();
                HashProvider.Clear();
            }

            // Step 6. Return the decrypted string in UTF8 format

            return UTF8.GetString(Results);
        }

        /// <summary>
        /// read the password from the password.txt file
        /// the file must exist
        /// </summary>
        /// <author>Shai Cantor</author>
        private void readPassword()
        {
            StreamReader reader = new StreamReader(m_passwordPath);
            while (!reader.EndOfStream)
            {
                string password = "";
                try
                {
                    password = reader.ReadLine();
                }
                catch
                {
                    reader.Close();
                }
                m_password = password;
            }
            reader.Close();
        }

        /// <summary>
        /// decrypt the encrypted urls in the blacklist.txt file
        /// and add them to the m_blacklist list
        /// </summary>
        /// <author>Etai Hazan</author>
        private void decryptBlackList()
        {
            StreamReader reader = null;
            try
            {
                reader = new StreamReader(m_blackListPath);
            }
            catch
            {
            }
            if (reader != null)
            {
                while (!reader.EndOfStream)
                {
                    string encryptedRestrictedSite = "";
                    try
                    {
                        encryptedRestrictedSite = reader.ReadLine();
                    }
                    catch
                    {
                        reader.Close();
                    }
                    string decryptedRestrictedSite = decryptString(encryptedRestrictedSite);
                    m_blackList.Add(decryptedRestrictedSite);
                }
                reader.Close();
            }
        }

        /// <summary>
        /// decrypt the encrypted urls in the whitelist.txt file
        /// and add them to the m_whitelist list
        /// </summary>
        /// <author>Etai Hazan</author>
        private void decryptWhiteList()
        {
            StreamReader reader = null;
            try
            {
                reader = new StreamReader(m_whiteListPath);
            }
            catch
            {

            }
            if (reader != null)
            {
                while (!reader.EndOfStream)
                {
                    string encryptedIP = "";
                    try
                    {
                        encryptedIP = reader.ReadLine();
                    }
                    catch
                    {
                        reader.Close();
                    }
                    string decryptedIP = decryptString(encryptedIP);
                    m_whiteList.Add(decryptedIP);
                }
                reader.Close();
            }
        }
    }
}
